package movee.spring.app.template.api.aspect;

import lombok.extern.slf4j.Slf4j;
import movee.spring.app.template.api.utils.ApiUtils;
import movee.spring.app.template.domain.api.ApiConstants;
import movee.spring.app.template.domain.api.ApiException;
import movee.spring.app.template.domain.api.ApiExceptionResponse;
import movee.spring.app.template.domain.api.ApiStatusCode;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;

/**
 * 统一处理API异常
 *
 * @author movee
 */
@Slf4j
public class BasicApiExceptionHandler {

    private final HttpServletRequest httpRequest;

    public BasicApiExceptionHandler(HttpServletRequest httpRequest) {
        this.httpRequest = httpRequest;
    }

    /**
     * 异常处理逻辑
     * @param t 捕获的异常
     * @return 异常时的响应
     */
    public ResponseEntity<ApiExceptionResponse> handleGenericExcetion(Throwable t) {
        ApiStatusCode statusCode = determineStatusCode(t);
        ApiExceptionResponse res = createExceptionResponse(t, statusCode);

        logException(t);

        // 构造响应
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.set(ApiConstants.X_API_REQUEST_ID, res.getRequestId());
        String date = LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME);
        httpHeaders.set(ApiConstants.X_API_DATE, date);
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        ResponseEntity<ApiExceptionResponse> responseEntity;
        responseEntity = new ResponseEntity<>(res, httpHeaders, statusCode.getHttpStatus());

        return responseEntity;
    }

    /**
     * 根据异常类型确定http响应码
     * @param t 捕获的异常
     * @return 异常时的响应
     */
    private ApiStatusCode determineStatusCode(Throwable t) {
        ApiStatusCode statusCode;
        if (t instanceof ApiException) {
            // 自定义的API异常，直接使用异常指定的响应码
            statusCode = ((ApiException) t).getApiStatusCode();
        } else if (t instanceof BindException
                || t instanceof ConstraintViolationException
                || t instanceof MethodArgumentNotValidException) {
            // JSR 303参数检查抛出的异常，返回请求参数错误
            statusCode = ApiStatusCode.INVALID_QUERY_PARAM;
        } else {
            // 未预期的异常，统一为内部错误（500）
            log.error("{}", ExceptionUtils.getStackTrace(t));
            statusCode = ApiStatusCode.INTERNAL_ERROR;
        }
        return statusCode;
    }

    /**
     * 生成异常时的响应
     * @param t
     * @param statusCode
     * @return
     */
    private ApiExceptionResponse createExceptionResponse(Throwable t, ApiStatusCode statusCode) {
        ApiExceptionResponse res = new ApiExceptionResponse();

        // 入口时生成的requestId
        String requestId = (String) httpRequest.getAttribute(ApiConstants.X_API_REQUEST_ID);
        if (!StringUtils.hasLength(requestId)) {
            requestId = UUID.randomUUID().toString();
        }

        res.setRequestId(requestId);
        res.setCode(statusCode.getCode());
        if (t instanceof MethodArgumentNotValidException) {
            // 提取参数检查失败的信息，添加到响应消息中
            BindingResult br = ((MethodArgumentNotValidException) t).getBindingResult();
            StringBuilder sb = new StringBuilder("totally find " + br.getErrorCount() + " errors: ");
            br.getAllErrors().forEach(e -> sb.append(e.getDefaultMessage() + ";"));
            res.setMessage(statusCode.getMessage() + sb);
        } else {
            // 未预期的异常，直接提取异常信息
            res.setMessage(statusCode.getMessage() + t.getMessage());
        }

        return res;
    }

    /**
     * 记录异常信息和测量请求处理时间
     * @param t 异常
     */
    private void logException(Throwable t) {
        // 请求源ip和请求信息：方法名和参数
        String requestRealIp = ApiUtils.getClientRealIp(httpRequest);
        String requestEntry = (String) httpRequest.getAttribute(ApiConstants.API_REQUEST_ENTRY);
        if (!StringUtils.hasLength(requestEntry)) {
            if (t instanceof BindException) {
                requestEntry = ((BindException) t).getObjectName();
            }
        }

        // 记录调用时间
        Instant start = (Instant) httpRequest.getAttribute(ApiConstants.API_REQUEST_START);
        if (start == null) {
            log.info("clientRealIp: {}, finish {}, duration time: 2 ms", requestRealIp, requestEntry);
        } else {
            Duration duration = Duration.between(start, Instant.now());
            log.info("clientRealIp: {}, finish {}, duration time: {} ms",
                    requestRealIp, requestEntry, duration.toMillis());
        }
    }

}
